package dnl.dev.memlog;

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.prefs.Preferences;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.math.stat.DescriptiveStatistics;
import org.jdesktop.swingx.JXTable;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import dnl.dev.memlog.parser.ClassInstanceCount;
import dnl.dev.memlog.parser.HistogramParser;
import dnl.jexem.prcs.ProcessInfo;
import dnl.jexem.prcs.util.ProcessSelectionDialog;
import dnl.ui.FileChooserUtils;
import dnl.util.ui.ProgressDialog;
import dnl.util.ui.ProgressListener;
import dnl.util.ui.WindowUtils;

/**
 * 
 * @author Daniel Ore
 * 
 */
public class Viewer extends JFrame {

	private static final String TITLE = "Memory Histograms Viewer.";
	private static DateFormat dateFormat = new SimpleDateFormat();
	private JFreeChart freeChart;
	private HistogramParser histogramParser;// = new HistogramParser();
	private JPanel centerJP = new JPanel(new BorderLayout());
	private JPanel statsJP = new JPanel(new BorderLayout());
	private JPanel chartJP = new JPanel(new BorderLayout());
	private JXTable classesTable = new JXTable();
	private JMenuBar menuBar;
	private JLabel classNameJL = new JLabel();
	// shows the raw stats numbers
	private JTextArea statsTA = new JTextArea();

	public Viewer() {
		setTitle(TITLE);
		initMenuBar();
		initPanels();
		initIcon();
	}

	// UI Initializations -------------------------------------------------

	private void initIcon() {
		URL iconUrl = getClass().getResource("memlog.gif");
		ImageIcon ii = new ImageIcon(iconUrl);
		this.setIconImage(ii.getImage());
	}

	private void initPanels() {
		showChart(null);
		JPanel classNamesPanel = getClassNamesPanel();
		initChartsJP(classNamesPanel);

		JSplitPane splitPane = new JSplitPane(SwingConstants.VERTICAL, classNamesPanel, statsJP);
		centerJP.add(BorderLayout.CENTER, splitPane);
		this.setContentPane(centerJP);
		splitPane.setDividerLocation(200);
	}

	private void initChartsJP(JPanel classNamesPanel) {
		classNamesPanel.setFont(classNamesPanel.getFont().deriveFont(Font.BOLD));
		this.statsJP.add(BorderLayout.SOUTH, classNameJL);
		JSplitPane splitPane = new JSplitPane(SwingConstants.HORIZONTAL, chartJP, new JScrollPane(
				statsTA));
		splitPane.setDividerLocation(400);
		this.statsJP.add(BorderLayout.CENTER, splitPane);
	}

	private void initMenuBar() {
		menuBar = new JMenuBar();
		JMenu fileMenu = new JMenu("File");
		
		JMenu sampleMenu = new JMenu("Sample");
		JMenuItem manualSampleingJM = new JMenuItem("Manual Sampling");
		JMenuItem periodicSampleingJM = new JMenuItem("Periodic Sampling");
		sampleMenu.add(manualSampleingJM);
		sampleMenu.add(periodicSampleingJM);
		manualSampleingJM.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				ProcessSelectionDialog dialog = new ProcessSelectionDialog(Viewer.this, true);
				dialog.showJavaProcesses();
				WindowUtils.centerWindowRelative(dialog, Viewer.this);
				dialog.setVisible(true);
				ProcessInfo selectedProcess = dialog.getSelectedProcess();
				if(selectedProcess != null){
					
				}
			}
		});
		
		JMenuItem open = new JMenuItem("Open");
		JMenu recentJM = new JMenu("Recent");
		open.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				Runnable r = new Runnable() {
					public void run() {
						handleHistogramDir(null);
					}
				};
				new Thread(r).start();
			}
		});
		fileMenu.add(open);
		fileMenu.add(recentJM);

		populateRecentDirsMenu(recentJM);

		menuBar.add(fileMenu);
		menuBar.add(sampleMenu);
		this.setJMenuBar(menuBar);
	}

	private void populateRecentDirsMenu(JMenu recentJM) {
		Preferences prefs = Preferences.userNodeForPackage(getClass());
		String recents = prefs.get("recentdirs", null);
		if (recents != null) {
			String[] dirs = StringUtils.split(recents, ";");
			for (final String dirStr : dirs) {
				JMenuItem recentDir = new JMenuItem(dirStr);
				recentDir.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent e) {
						Runnable r = new Runnable() {
							public void run() {
								handleHistogramDir(new File(dirStr));
							}
						};
						new Thread(r).start();
					}
				});
				recentJM.add(recentDir);
			}
		}
	}

	private JPanel getClassNamesPanel() {
		JPanel jp = new JPanel(new BorderLayout());
		classesTable.setModel(new DefaultTableModel(new String[] { "" }, 10));
		classesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent e) {
				if (e.getValueIsAdjusting())
					return;
				int modelIndex = classesTable.convertRowIndexToModel(classesTable.getSelectedRow());
				String selectedClass = (String) classesTable.getModel().getValueAt(modelIndex, 0);
				showChart(selectedClass);
			}
		});
		jp.add(BorderLayout.CENTER, new JScrollPane(classesTable));
		jp.setBorder(BorderFactory.createTitledBorder("Classes"));

		JPanel jp2 = new JPanel(new GridLayout(2, 1));
		final JCheckBox fqnCheckBox = new JCheckBox("Fully qualified names.", true);
		fqnCheckBox.addItemListener(new ItemListener() {
			@Override
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED) {
					JOptionPane.showMessageDialog(Viewer.this, "Not implemented yet");
				}
			}
		});
		final JTextField filterTF = new JTextField(2);
		filterTF.setBorder(BorderFactory.createTitledBorder("Filter"));
		filterTF.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				populateClassesTable(filterTF.getText());
			}
		});
		jp2.add(fqnCheckBox);
		jp2.add(filterTF);
		jp.add(BorderLayout.SOUTH, jp2);

		return jp;
	}

	//--------------------------------------------------------------------------

	/**
	 * 
	 */
	private void handleHistogramDir(File dir) {
		File selectedDirectory = dir;
		if (selectedDirectory == null) {
			selectedDirectory = FileChooserUtils.selectDirectory(Viewer.this);
			if (selectedDirectory == null) {
				return;
			}
			registerRecentDir(selectedDirectory);
		}
		@SuppressWarnings("unused")
		ProgressListener progressListener = new ProgressListener() {
			@Override
			public void finished() {
			}

			@Override
			public void progressed(int precentage) {
				ProgressDialog.showProgress(Viewer.this, "Processing histograms dir", precentage);
			}
		};
		//histogramParser.parse(selectedDirectory, progressListener);
		ProgressDialog.setProgressText(Viewer.this, "Populating UI");
		populateClassesTable(null);
		ProgressDialog.finished(Viewer.this);
		setTitle(TITLE + "  " + selectedDirectory.getAbsolutePath());
	}

	private void registerRecentDir(File selectedDirectory) {
		Preferences prefs = Preferences.userNodeForPackage(getClass());
		String recents = prefs.get("recentdirs", null);
		if (recents != null) {
			String[] strings = StringUtils.split(recents, ";");
			List<String> l = new ArrayList<String>(Arrays.asList(strings));
			l.add(0, selectedDirectory.getAbsolutePath());
			if (l.size() > 6) {
				l.remove(l.size() - 1);
			}
			prefs.put("recentdirs", StringUtils.join(l.toArray(), ";"));
		} else {
			prefs.put("recentdirs", selectedDirectory.getAbsolutePath());
		}
	}

	private void populateClassesTable(String filter) {
		Set<String> classNames = histogramParser.getClassNames();
		List<String> cn = new ArrayList<String>();
		for (String className : classNames) {
			if (filter == null || className.startsWith(filter)) {
				cn.add(className);
			}
		}

		List<StatisticalData> statisticalData = calcVariances(cn);
		TableModel tableModel = new TModel(cn, statisticalData);
		classesTable.setModel(tableModel);
	}

	private List<StatisticalData> calcVariances(List<String> classNames) {
		List<StatisticalData> variances = new ArrayList<StatisticalData>();
		for (String className : classNames) {
			StatisticalData statisticalData = calculateStatisticalData(className);
			variances.add(statisticalData);
		}
		return variances;
	}

	private StatisticalData calculateStatisticalData(String className) {
		DescriptiveStatistics stats = DescriptiveStatistics.newInstance();
		List<ClassInstanceCount> histoStats = histogramParser.getStats(className);

		for (ClassInstanceCount dateAndNumber : histoStats) {
			stats.addValue(dateAndNumber.getNumberOfInstances());
		}
		double variance = stats.getVariance();
		int mean = (int) stats.getMean();
		int absoluteVariance = (int) Math.floor(variance / mean);
		StatisticalData statisticalData = new StatisticalData(false, mean, absoluteVariance);
		return statisticalData;
	}

	public void showChart(String className) {
		IntervalXYDataset dataset = null;
		if (className != null) {
			dataset = createDataset(className);
		}
		freeChart = ChartFactory.createXYBarChart("Object Counts", "time (sec)", false,
				"number of instances", dataset, PlotOrientation.VERTICAL, false, false, false);
		ChartPanel chartPanel = new ChartPanel(freeChart);
		classNameJL.setText("Showing mem histo for class: " + className);
		this.chartJP.removeAll();
		this.chartJP.add(BorderLayout.CENTER, chartPanel);
		this.chartJP.doLayout();

		if (className != null) {
			List<ClassInstanceCount> stats = histogramParser.getStats(className);
			StringBuilder sb = new StringBuilder();
			for (ClassInstanceCount dateAndNumber : stats) {
				sb.append(dateFormat.format(dateAndNumber.getSamplingDate()));
				sb.append("=");
				sb.append(dateAndNumber.getNumberOfInstances());
				sb.append("\n");
			}
			statsTA.setText(sb.toString());
		}
	}

	private IntervalXYDataset createDataset(String className) {
		final XYSeries series = new XYSeries(className);
		List<ClassInstanceCount> histoStats = histogramParser.getStats(className);
		Date firstDate = resolveFirstDate(histoStats);
		long firstSec = firstDate.getTime() / 1000;

		for (ClassInstanceCount dateAndNumber : histoStats) {
			long timeSec = dateAndNumber.getSamplingDate().getTime() / 1000;
			series.add(timeSec - firstSec, dateAndNumber.getNumberOfInstances());
		}
		final XYSeriesCollection dataset = new XYSeriesCollection(series);
		return dataset;
	}

	private Date resolveFirstDate(List<ClassInstanceCount> histoStats) {
		Date firstDate = null;
		for (ClassInstanceCount dateAndNumber : histoStats) {
			long time = dateAndNumber.getSamplingDate().getTime();
			if (firstDate == null || firstDate.getTime() > time) {
				firstDate = dateAndNumber.getSamplingDate();
			}
		}
		return firstDate;
	}

	public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {
			e.printStackTrace();
		}
		Viewer viewer = new Viewer();
		viewer.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		WindowUtils.setWindowSize(viewer, .75, .5);
		WindowUtils.centerWindow(viewer);
		viewer.setVisible(true);
	}
}
